package tam;

import java.awt.Color;
import robocode.HitByBulletEvent;
import robocode.HitRobotEvent;
import robocode.HitWallEvent;
import robocode.Robot;
import robocode.ScannedRobotEvent;

/**
 * Ringer robot. Uses a simple stalling strategy. Locks on to a single enemy and tracks
 * its behavior. When target shoots, or if target gets too close, Ringer dodges to the
 * left or right. The distance dodged is inversely proportional to the distance between
 * Ringer and the target, as dodging shots at a greater distance requires less movement.
 * Assumes all shots are aimed directly at Ringer's current position. While not dodging,
 * remains still. The goal is to appear to be an easy target and cause the enemy to expend
 * its energy attempting to shoot it. Ringer will not fire until the enemy runs out of
 * energy and stalls, or if there just happens to be a very good opening available.
 * 
 * Ringer is fairly ineffective against enemies that stay in close range. To partially
 * compensate for this, it has a simple melee mode that it will switch into if the target
 * is consistently in close range. The behavior in this mode is similar to that of the
 * sample robot RamFire. It will charge directly at the enemy and fire when it gets into range.
 * 
 * @author Micah
 *
 */
public class Ringer extends Robot {
  
  // Class variables.
  private String targetName = "";
  private double targetEnergy = 0;
  private boolean moveForward = true;
  private boolean turnRadarRight = true;
  private boolean hitByBullet = false;
  private boolean changedDirections = false;
  private boolean meleeMode = false;
  private int meleeDistanceCounter = 0;
  private int meleeDistanceThreshhold = 200;
  private int maxMeleeCount = 20;
  private int maxWallDistance = 50;
  private int minMoveDistance = 30;
  private int maxMoveDistance = 100;
  private int maxEnemyDistance = 300;
  private int fireDistance = 200;
  private int fireAngle = 45;
  private double firePower = 3;
  
  
  @Override
  public void run() {
    
    // Color tank.
    setBodyColor(Color.white);
    setGunColor(Color.white);
    setRadarColor(Color.orange);
    
    // Initial scan for target.
    this.turnRadarRight(359);
    
    // Main loop.
    while (true) {
      
      if (this.meleeMode) {
        this.meleeBehavior();
      }
      else {
        this.normalBehavior();
      }
    }
  }
  
  @Override
  public void onScannedRobot(ScannedRobotEvent e) {
    
    // Toggle direction of gun/radar turn.
    this.toggleRadarDirection();
    
    // If no target is selected, make detected robot target.
    if (this.targetName.equals("")) {
      this.targetName = e.getName();
    }
    
    // If not in melee mode, dodge shots and try to wait out target.
    if (this.targetName.equals(e.getName())) {
      
      // Melee mode. Entered into if target robot is consistently in close range.
      // acts similarly to the sample robot RamFire. Charges at target and fires
      // when they are in range.
      if (this.meleeMode) {
        this.meleeAttack(e);
        return;
      }
      
      // If target is already found, check if energy <= 0. If so, fire at them in order to
      // make sure they are dead, and then reset targetName so a new target can be chosen.
      if (e.getEnergy() <= 0) {
        this.finishEnemy(e);
        return;
      }
    
      // If targetEnergy has not been set, set it.
      if (this.targetEnergy <= 0) {
        this.targetEnergy = e.getEnergy();
      }
      
      // If tank somehow ends up facing an enemy while dodging and is close, fire.
      if (e.getDistance() <= this.fireDistance && Math.abs(e.getBearing()) <= fireAngle) {
        this.fire(this.firePower);
      }
      
      // If it looks as though target has fired, or is getting close,
      // try to move away from it.
      this.dodgeShots(e);
      
      // Always try to be oriented perpendicular to the target.
      this.turnRight(e.getBearing() + 90);
      
      // If target is in close range, add one to the melee mode counter.
      // If/When melee mode counter gets high enough, go into melee mode.
      this.checkMeleeMode(e);
    }
  }
  
  @Override
  public void onHitByBullet(HitByBulletEvent event) {
    // If hit by bullet, set flag.
    this.hitByBullet = true;
  }
  
  @Override
  public void onHitRobot(HitRobotEvent event) {
    //If another robot is hit, check if they are the firing angle and shoot.
    if (Math.abs(event.getBearing()) <= this.fireAngle) {
      this.fire(this.firePower);
    }
  }
  
  @Override
  public void onHitWall(HitWallEvent event) {
    // In the event of a wall collision, reverse direction.
    this.changedDirections = true;
    
    if (this.moveForward) {
      this.moveForward = false;
    }
    else {
      this.moveForward = true;
    }
  }
  
  /**
   * Checks if robot will hit a wall, based on it's current position, orientation,
   * movement direction (forwards or backwards), and internal threshold values
   * indicating how close it is allowed to get to any of the walls.
   * 
   * @return A boolean indicating whether or not a wall collision is predicted.
   */
  public boolean isGoingToHitWall() {
    
    double distanceFromLeft = this.getX();
    double distanceFromRight = this.getBattleFieldWidth() - this.getX();
    double distanceFromTop = this.getBattleFieldHeight() - this.getY();
    double distanceFromBottom = this.getY();
    
    // Situation where tank is facing generally upwards.
    if (this.getHeading() >= 315 || this.getHeading() <= 45) {
      
      if (this.moveForward && distanceFromTop < this.maxWallDistance) {
        return true;
      }
      if (!this.moveForward && distanceFromBottom < this.maxWallDistance) {
         return true;
      }
      return false;
      
    }
    // Situation where tank is facing generally rightwards.
    else if (this.getHeading() >= 45 && this.getHeading() <= 135) {
      
      if (this.moveForward  && distanceFromRight < this.maxWallDistance) {
        return true;
      }
      if (!this.moveForward && distanceFromLeft < this.maxWallDistance) {
        return true;
      }
      return false;
      
    }
    // Situation where tank is facing generally downwards.
    else if (this.getHeading() >= 135 && this.getHeading() <= 225) {
      
      if (this.moveForward && distanceFromBottom < this.maxWallDistance) {
        return true;
      }
      if (!this.moveForward && distanceFromTop < this.maxWallDistance) {
        return true;
      }
      return false;
      
    }
    // Situation where tank is facing generally leftwards.
    else {
      
      if (this.moveForward && distanceFromLeft < this.maxWallDistance) {
        return true;
      }
      if (!this.moveForward && distanceFromRight < this.maxWallDistance) {
        return true;
      }
      return false;
      
    }
  }
  
  /**
   * Toggles the turnRadarRight variable. If it is true, it becomes false, and vice-versa.
   */
  public void toggleRadarDirection() {
    if (this.turnRadarRight) {
      this.turnRadarRight = false;
    }
    else {
      this.turnRadarRight = true;
    }
  }
  
  /**
   * The actions performed in melee mode when a target is scanned.
   * @param e The ScannedRobotEvent in which the target was detected.
   */
  public void meleeAttack(ScannedRobotEvent e) {
    this.turnRight(e.getBearing());
    if (e.getDistance() > this.fireDistance) {
      this.ahead(e.getDistance());
    }
    if (e.getDistance() <= this.fireDistance && Math.abs(e.getBearing()) <= this.fireAngle) {
      this.fire(this.firePower);
    }
  }
  
  /**
   * The actions performed when not in melee mode. That is, while waiting for
   * and attempting to dodge shots fired by the target (and the target itself).
   * 
   * @param e The ScannedRobotEvent in which the target was detected. 
   */
  public void dodgeShots(ScannedRobotEvent e) {
    
    // Only perform any action if a shot may have been fired, if
    // tank was hit by a shot, or if target is in close range.
    if (e.getEnergy() != this.targetEnergy ||
        this.hitByBullet ||
        e.getDistance() < this.maxEnemyDistance) {
      
      // Determine if direction should be changed.
      if (this.isGoingToHitWall()) {
        this.changedDirections = true;
        
        if (this.moveForward) {
          this.moveForward = false;
        }
        else {
          this.moveForward = true;
        }
      }
      
      // Move in chosen direction. Movement distance scales inversely with how far
      // the target is. Tank will also move twice as much if it just changed directions.
      // If it does not, it may simply move back into the path of a bullet it just attempted
      // to dodge.
      int moveMin = this.minMoveDistance;
      double moveMax = this.maxMoveDistance * (e.getDistance() /
                       Math.hypot(this.getBattleFieldWidth(), this.getBattleFieldHeight()));
      double toMove = this.maxMoveDistance - Math.max(moveMin, moveMax);
      
      if (this.changedDirections) {
        toMove *= 2;
      }
      
      if (this.moveForward) {
        this.ahead(toMove);
      }
      else {
        this.back(toMove);
      }
      
      // Housekeeping.
      this.targetEnergy = e.getEnergy();
      this.hitByBullet = false;
      this.changedDirections = false;
    }
  }
  
  /**
   * Checks whether or not target has been in close range for long enough to
   * cause a switch into melee mode.
   * 
   * @param e The ScannedRobotEvent in which the target was detected.
   */
  public void checkMeleeMode(ScannedRobotEvent e) {
    if (e.getDistance() <= this.meleeDistanceThreshhold) {
      this.meleeDistanceCounter++;
    }
    if (this.meleeDistanceCounter >= this.maxMeleeCount) {
      this.meleeMode = true;
    }
  }
  
  /**
   * If target has been successfully stalled and is out of energy,
   * fire at it and reset the target name/energy values so that
   * a new target can be chosen.
   * 
   * @param e The ScannedRobotEvent in which the target was detected.
   */
  public void finishEnemy(ScannedRobotEvent e) {
    this.turnRight(e.getBearing());
    this.fire(3);
    this.targetName = "";
    this.targetEnergy = 0;
  }
  
  /**
   * Behavior in main loop when not in melee mode.
   */
  public void normalBehavior() {
    if (this.turnRadarRight) {
      this.turnRadarRight(45);
    }
    else {
      this.turnRadarLeft(45);
    }
  }
  
  /**
   * Behavior in main loop when in melee mode.
   */
  public void meleeBehavior() {
    if (this.getRadarHeading() != this.getHeading()) {
      this.turnRadarRight(this.getHeading() - this.getRadarHeading());
      this.setAdjustRadarForRobotTurn(false);
    }
    this.turnRight(359);
  }
  
  /**
   * Returns whether or not robot is set to move forward or backward.
   * 
   * @return True if robot is set to move forward, false otherwise.
   */
  public boolean isMovingForward() {
    return this.moveForward;
  }
  
  /**
   * Returns whether or not robot is in melee mode.
   * 
   * @return True if melee mode is active.
   */
  public boolean isInMeleeMode() {
    return this.meleeMode;
  }
  
  /**
   * Returns the counter used to determined if robot should switch to melee mode.
   * 
   * @return The number of times target was spotted in melee range.
   */
  public int getMeleeModeCounter() {
    return this.meleeDistanceCounter;
  }
  
  /**
   * Returns the threshold used to determine if robot should switch to melee mode.
   * 
   * @return The number of times target can be spotted in melee range before switching to melee.
   */
  public int meleeModeCounterThreshold() {
    return this.maxMeleeCount;
  }
}